﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TanmiaGrp.Basis.Core;
using TanmiaGrp.GeneralLedger.WinApp.Commands;
using TanmiaGrp.Basis;
using System.Windows.Input;
using System.Windows;
using TanmiaGrp.Basis.Properties;
using System.Windows.Data;
using TanmiaGrp.GeneralLedger.Domain;
using TanmiaGrp.GeneralLedger.Core;

namespace TanmiaGrp.GeneralLedger.WinApp
{
    public class BaseBrowserViewModel<TBusinessObject, TBusinessDomain> : BaseViewModel<TBusinessObject, TBusinessDomain>
        where TBusinessObject : BusinessObject, new()
        where TBusinessDomain : BaseDomain<TBusinessObject, TBusinessDomain>, new()
    {
        #region Private Members
        private ICollectionView entities;
        private INotifyTaskCompletion<GetPageAsyncResponse> entitiesTask;
        private string pageNumber;
        private string searchText;
        private SearchSpecification searchSpecification;

        #endregion

        #region Public Properties
        /// <summary>
        /// Gets a command which represent refreshing of the BrowserView.
        /// </summary>
        public ICommand RefreshCommand { get; protected set; }

        /// <summary>
        /// Gets the command to invoke when (Delete) button is pressed.
        /// </summary>
        public ICommand DeleteCommand { get; protected set; }

        /// <summary>
        /// Represents Previous, and First page navigation commands.
        /// </summary>
        public ICommand GoToForthPageCommand { get; protected set; }

        /// <summary>
        /// Represents Next, and Last page navigation commands.
        /// </summary>
        public ICommand GoToBackPageCommand { get; protected set; }

        /// <summary>
        /// Represents open editor view to edit current entity command.
        /// </summary>
        public ICommand EditCommand { get; protected set; }

        /// <summary>
        /// Represents open editor view to add new entity command.
        /// </summary>
        public ICommand AddCommand { get; protected set; }

        /// <summary>
        /// Gets or sets a CollectionView of all entities which will be shown in the browser view.
        /// </summary>
        public ICollectionView Entities
        {
            get { return entities; }
            set
            {
                if (entities != value)
                {
                    // un-wire event handlers to avoid memory leak.
                    if (entities != null)
                        entities.CurrentChanged -= OnCurrentEntityChanged;

                    // set new value of entities, mostly after Refresh.
                    entities = value;

                    // wire event handlers.
                    entities.CurrentChanged += OnCurrentEntityChanged;

                    // notify that property changed.
                    RaisePropertyChanged(() => Entities);
                }
            }
        }

        /// <summary>
        /// Notifiable completion task to retrieve page of entities.
        /// </summary>
        public INotifyTaskCompletion<GetPageAsyncResponse> EntitiesTask
        {
            get { return entitiesTask; }
            private set
            {
                if (entitiesTask != value)
                {
                    // un-wire event handlers to avoid memory leak.
                    if (entitiesTask != null)
                        entitiesTask.PropertyChanged -= OnEntitiesTaskChanged;

                    // set new task value, from PopulatePage method.
                    entitiesTask = value;

                    // wire event handler to observe completion of task.
                    entitiesTask.PropertyChanged += OnEntitiesTaskChanged;

                    // Notify that task changed.
                    RaisePropertyChanged(() => EntitiesTask);
                }
            }
        }

        /// <summary>
        /// current page number to be shown on user interface.
        /// </summary>
        public string PageNumber
        {
            get { return pageNumber; }
            set
            {
                if (pageNumber != value)
                {
                    pageNumber = value;
                    RaisePropertyChanged(() => PageNumber);
                }
            }
        }

        /// <summary>
        /// indicate current PageIndex from 0 - PageCount.
        /// </summary>
        public int PageIndex { get; set; }

        /// <summary>
        /// Returns count of pages, that can user navigate with it.
        /// DONOT call this property before fill totalCount field.
        /// </summary>
        public int PageCount { get; set; }

        /// <summary>
        /// Gets or sets the progress service.
        /// </summary>
        public IProgress ProgressObj { get; set; }

        /// <summary>
        /// Gets or sets the user message service.
        /// </summary>
        public IUserMessageService UserMessageSvc { get; set; }

        /// <summary>
        /// Gets all entities which are selected in the UI.
        /// </summary>
        /// <remarks>
        /// This property have to be overridden in derived class to specify how the selected entities should be returned.
        /// </remarks>
        public virtual IEnumerable<TBusinessObject> SelectedEntities
        {
            get { return ((List<TBusinessObject>)Entities.SourceCollection).Where(x => x.IsSelected); }
        }

        public string SearchText
        {
            get { return searchText; }
            set
            {
                if (searchText != value)
                {
                    searchText = value;
                    RaisePropertyChanged(() => SearchText);
                }
            }
        }

        public SearchSpecification SearchSpecification
        {
            get { return searchSpecification; }
            set
            {
                if (searchSpecification != value)
                {
                    searchSpecification = value;
                    RaisePropertyChanged(() => SearchSpecification);
                }
            }
        }
        #endregion

        public BaseBrowserViewModel()
        {
            SearchSpecification = new Core.SearchSpecification();
            SearchSpecification.PropertyChanged += SearchSpecificationChangedHandler;
            // x. Mock Initialization
            ProgressObj = new MockProgress();
            UserMessageSvc = new UserMessageService();

            // x. Wire Commands
            RefreshCommand = new DelegateCommand<NavigationMoveEnum?>(OnRefresh);
            DeleteCommand = new DelegateCommand(OnDelete, CanDelete);
            GoToForthPageCommand = new DelegateCommand<NavigationMoveEnum?>(OnPageNavigate, CanPageNavigateForth);
            GoToBackPageCommand = new DelegateCommand<NavigationMoveEnum?>(OnPageNavigate, CanPageNavigateBack);
            EditCommand = new DelegateCommand<TBusinessObject>(OnEdit, CanEdit);
            AddCommand = new DelegateCommand<TBusinessObject>(OnAdd);
            CloseCommand = new DelegateCommand<object>(OnCloseHandler);
        }

        /// <summary>
        /// Called when any property of search specification changed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void SearchSpecificationChangedHandler(object sender, PropertyChangedEventArgs e)
        {
            OnRefresh(NavigationMoveEnum.KeepCurrent);
        }

        protected override void Initialize()
        {
            // x. Wire events
            EntitySavedOrDeletedEvent.Register<TBusinessObject>(OnEditorRefresh);
            EntityNavigationEvent.Register<NavigationMoveEnum?, BrowserStateChangedEventArgs>(OnEntityNavigate);

            // x. start task to retrieve page of entities.
            OnRefresh(NavigationMoveEnum.None);
        }

        #region Core Logic
        #endregion Core Logic

        #region Main Business Logic

        /// <summary>
        /// Determines if OnDelete can be executed or not.
        /// </summary>
        /// <returns>True, if OnDelete can be deleted. False, otherwise.</returns>
        protected virtual bool CanDelete()
        {
            if (Entities == null)
                return false;

            return Entities.CurrentItem != null;
        }

        /// <summary>
        /// Delete (SelectedEntities) from the repository.
        /// </summary>
        protected virtual async void OnDelete()
        {
            MessageBoxResult userResponse = UserMessageSvc.RequestUserConfirmation("هل أنت متأكد من حذف المجموعة/المجموعات المحددة؟", "تأكيد حذف");
            if (userResponse == MessageBoxResult.No)
                return;

            int entityCount = SelectedEntities.Count();
            try
            {
                bool deletionResult = await BusinessDomain.DeleteAsync<TBusinessObject, TBusinessDomain>(SelectedEntities);
                if (deletionResult == false)
                    throw new PresentationException("حدث خطأ أثناء حذف المجموعة/المجموعات");

                OnRefresh(NavigationMoveEnum.None);

                UserMessageSvc.SendStatusMessage(deletionResult, BaseResources.DeleteSucceed, BaseResources.DeleteUnsucceed, entityCount, DisplayEntityName);
            }
            catch (BaseAppException appEx)
            {
                UserMessageSvc.ShowErrorMessage(appEx);
            }
        }

        /// <summary>
        /// Called when Close command is executed.
        /// </summary>
        protected override void OnCloseHandler(object sender)
        {
            Close(sender);
        }

        /// <summary>
        /// Refresh (Entities) property with new values from repository.
        /// </summary>
        /// <param name="pageMove">Determines where to move from current page.</param>
        protected virtual void OnRefresh(NavigationMoveEnum? pageMove)
        {
            // preventing multiple refresh.
            if (ProgressObj.IsProgress)
                return;

            ProgressObj.ShowProgress("تحديث مستعرض المجموعات...");

            GetPageAsyncRequest request = new GetPageAsyncRequest
            {
                Move = pageMove.Value,
                PageSize = ConfigManager.Obj.PageSize,
                Index = PageIndex
            };
            EntitiesTask = NotifyTaskCompletion.Create<GetPageAsyncResponse>(() => GetPageAsync(request));
        }

        /// <summary>
        /// Determines if Edit current entity is available.
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected virtual bool CanEdit(TBusinessObject arg)
        {
            return arg != null;
        }

        /// <summary>
        /// Called when EditCommand is executed.
        /// Open EditorView with controls populated with the passed object.
        /// </summary>
        /// <param name="obj">CurrentItem that represent business entity from grid.</param>
        protected virtual void OnEdit(TBusinessObject arg)
        {
            arg.CaptureState();

            UpdateCurrentViewEvent
                .Send(arg)
                .Fire(EditorView);

            RaiseBrowserStateChangedEvent();
        }

        /// <summary>
        /// Called when Add new entity command is executed.
        /// Open EditorView with clean controls to create new BusinessEntity.
        /// </summary>
        protected virtual void OnAdd(TBusinessObject arg)
        {
            TBusinessObject newEntity = new TBusinessObject();
            newEntity.CaptureState();

            UpdateCurrentViewEvent
                .Send<TBusinessObject>(newEntity)
                .Fire(EditorView);

            RaiseBrowserStateChangedEvent();
        }

        /// <summary>
        /// Determines if page navigation can go to first, or previous page.
        /// </summary>
        /// <returns></returns>
        protected virtual bool CanPageNavigateForth(NavigationMoveEnum? move)
        {
            return PageIndex > 0;
        }

        /// <summary>
        /// Determines if page navigation can go to last, or next page.
        /// </summary>
        /// <returns></returns>
        protected virtual bool CanPageNavigateBack(NavigationMoveEnum? move)
        {
            // at constructor OnRefresh is called, so => TotalCount is filled.
            return PageIndex < PageCount;
        }

        /// <summary>
        /// Called when PageNavigationCommands are executed.
        /// PageNavigationCommands = (GoToForthPageCommand, and GoToBackPageCommand).
        /// </summary>
        /// <param name="move"></param>
        protected virtual void OnPageNavigate(NavigationMoveEnum? move)
        {
            OnRefresh(move);
        }

        #endregion Main Business Logic

        #region Event Handlers

        /// <summary>
        /// Called when EditorViewModel request to navigate to another Entity in BrowserViewModel.Entities.
        /// </summary>
        /// <param name="param">NavigationMoveEnum variable.</param>
        protected virtual BrowserStateChangedEventArgs OnEntityNavigate(NavigationMoveEnum? move)
        {
            if (move == NavigationMoveEnum.First)
                Entities.MoveCurrentToFirst();

            else if (move == NavigationMoveEnum.Last)
                Entities.MoveCurrentToLast();

            else if (move == NavigationMoveEnum.Next)
                Entities.MoveCurrentToNext();

            else if (move == NavigationMoveEnum.Previous)
                Entities.MoveCurrentToPrevious();

            return new BrowserStateChangedEventArgs
            {
                IsAtFirst = Entities.IsAtFirst(),
                IsAtLast = Entities.IsAtLast(),
                CurrentEntity = Entities.CurrentItem
            };
        }

        /// <summary>
        /// Called when select item on the grid changed.
        /// </summary>
        protected virtual void OnCurrentEntityChanged(object sender, EventArgs e)
        {
            UpdateUI();
        }

        /// <summary>
        /// Called when NotifiedTask that retrieve page from repository is completed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected virtual void OnEntitiesTaskChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "IsSuccessfullyCompleted")
            {
                GetPageAsyncResponse response = EntitiesTask.Result;
                Entities = response.Entities;
                PageIndex = response.AdjustedIndex;
                PageCount = response.PageCount;
                PageNumber = string.Format("{0} - {1}", response.AdjustedIndex + 1, response.PageCount + 1);

                // when this is first Refresh, at construction of ViewModel
                if (response.Request.Move == NavigationMoveEnum.None)
                {
                    OnBrowserInitialized(Entities.IsAtFirst(), Entities.IsAtLast(), Entities.CurrentItem);
                    RaiseBrowserStateChangedEvent();
                }
                else if (response.Request.Move != NavigationMoveEnum.None)
                {
                    OnPageNavigation(NavigationMoveEnum.First);
                }
            }
            else if (e.PropertyName == "ErrorMessage")
            {
                UserMessageSvc.ShowErrorMessage(EntitiesTask.InnerException as BaseAppException);
            }

            UpdateUI();

            ProgressObj.HideProgress();
        }

        /// <summary>
        /// Called when EditorView request BrowserRefresh.
        /// </summary>
        /// <param name="param">BusinessEntity that is changed.</param>
        protected virtual void OnEditorRefresh(TBusinessObject param)
        {
            OnRefresh(NavigationMoveEnum.KeepCurrent);
        }

        #endregion

        #region Helper Methods

        /// <summary>
        /// Retrieve Page of entities from repository asynchronously.
        /// </summary>
        /// <param name="request"></param>
        /// <returns></returns>
        private async Task<GetPageAsyncResponse> GetPageAsync(GetPageAsyncRequest request)
        {
            GetPageAsyncResponse response = new GetPageAsyncResponse() { Request = request };

            await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);

            // retrieve total entity count from repository.
            response.TotalCount = await BusinessDomain.GetTotalCountAsync();

            // calculate PageCount according to PageSize.
            response.PageCount = response.TotalCount / request.PageSize;
            if (response.PageCount > 0 && response.TotalCount % request.PageSize == 0)
                response.PageCount--;

            // assign AdjustedIndex and adjust it.
            response.AdjustedIndex = request.Index;

            if (request.Move == NavigationMoveEnum.First)
                response.AdjustedIndex = 0;

            else if (request.Move == NavigationMoveEnum.Previous)
                response.AdjustedIndex--;

            else if (request.Move == NavigationMoveEnum.Last)
                response.AdjustedIndex = response.PageCount;

            else if (request.Move == NavigationMoveEnum.Next)
                response.AdjustedIndex++;

            if (response.AdjustedIndex > response.PageCount)
                response.AdjustedIndex = response.PageCount;

            SearchSpecification.PageIndex = response.AdjustedIndex;

            // get page at AdjustedIndex.
            response.Entities = MapEntities(await BusinessDomain.SearchPageAsync(SearchSpecification));

            return response;
        }

        protected virtual ICollectionView MapEntities(List<TBusinessObject> entities)
        {
            return CollectionViewSource.GetDefaultView(entities);
        }

        /// <summary>
        /// Adjust PageIndex indicator according to Move Command from UI, and Show the page Number on UI.
        /// </summary>
        /// <param name="pageMove"></param>
        protected virtual void UpdateUI()
        {
            GoToBackPageCommand.RaiseCanExecuteChanged();
            GoToForthPageCommand.RaiseCanExecuteChanged();
            DeleteCommand.RaiseCanExecuteChanged();
            EditCommand.RaiseCanExecuteChanged();
        }

        #endregion

        protected virtual void RaiseBrowserStateChangedEvent()
        {
            OnBrowserStateChanged(new BrowserStateChangedEventArgs { IsAtFirst = Entities.IsAtFirst(), IsAtLast = Entities.IsAtLast(), CurrentEntity = Entities.CurrentItem });
        }
    }
}
